﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using System.Xml.XPath;
using DevDefined.OAuth.Framework;
using Lightray.Hattrick.ChppLib.EntityTypes;
using Lightray.Hattrick.ChppLib.OAuth;

namespace Lightray.Hattrick.ChppLib
{
    // TODO: consts for website or generic builder of protected url
    // needs some sort of internal xml cache check not to download same xml twice (in case of matches and their teams)
    public class WebFetcher
    {
        private const string CONSUMER_KEY = "JSbyUK14ITfQDTSQOsHFU-";
        private DevDefinedOAuth _auth;
        private Queue<FetchRequestQueueItem> _fetchQueue;
        private Queue<FetchRequestQueueItem> _userFetchQueue;
        private FetchResultEventArgs _fetchResult;
        private FetchRequest _lastRequest;
        private List<XDocument> _xmls;
        private bool _fetchingLiveFirstTime;

        public volatile bool _fetching;
        public bool Fetching { get { return _fetching; } }

        #region Connection Variables
        private int _myTeamLeagueId = -1;
        private int _myTeamId = -1;
        private int _myUserId = -1;
        private IEnumerable<int> _myLeagueOtherTeamIds;
        #endregion

        private class FetchRequestQueueItem
        {
            public Action<FetchRequestQueueItem> PreFetchCallback { get; set; }
            public Action<XDocument> FetchFinishedCallback { get; set; }
            public string UrlToFetch { get; set; }
            public bool DummyItem { get; set; }

            public FetchRequestQueueItem()
            {
                DummyItem = false;
                UrlToFetch = string.Empty;
                PreFetchCallback = null;
                FetchFinishedCallback = null;
            }
        }

        public event EventHandler<FetchResultEventArgs> FetchComplete;
        public event EventHandler<EventArgs> FetchProgress;
        public event Func<FetchRequest, int> FetchParameterNeeded;

        public WebFetcher(IToken chppToken, string chppConsumerSecret)
        {
            _auth = new DevDefinedOAuth(CONSUMER_KEY, chppConsumerSecret);
            _auth.AccessToken = chppToken;
            _fetchQueue = new Queue<FetchRequestQueueItem>();
            _userFetchQueue = new Queue<FetchRequestQueueItem>();
            _fetching = false;
            _fetchingLiveFirstTime = true;
            _xmls = new List<XDocument>();
        }

        public void Fetch(FetchRequest request)
        {
            _lastRequest = request;

            if (isRequested(request, FetchRequest.MyTeam))
                queueFetchMyTeam();

            if (isRequested(request, FetchRequest.MyCountry))
            {
                makeSureMyTeamIsFetched(request);                /* _myTeamLeagueId needed */
                queueFetchMyCountry();
            }

            if (isRequested(request, FetchRequest.MyPlayers))
                queueFetchMyPlayers();

            if (isRequested(request, FetchRequest.MyYouthPlayers))
                queueFetchMyYouthPlayers();

            if (isRequested(request, FetchRequest.MyLeague))
            {
                makeSureMyTeamIsFetched(request);                 /* _myTeamId needed */
                queueFetchMyLeague();
            }

            if (isRequested(request, FetchRequest.MyRegion))
                queueFetchMyRegion();

            if (isRequested(request, FetchRequest.MyEconomy))
                queueFetchMyEconomy();

            if (isRequested(request, FetchRequest.MyLeagueOtherTeams))
                queueFetchMyLeagueOtherTeams();

            if (isRequested(request, FetchRequest.MyMatches))
                queueFetchMyMatches();

            if (isRequested(request, FetchRequest.Live))
                queueFetchLive();
            else
                _fetchingLiveFirstTime = true;

            transferUserQueueToFetchQueue();

            if (hasSomethingToFetch())
                new Thread(fetchThread).Start();
        }

        private void makeSureMyTeamIsFetched(FetchRequest request)
        {
            if (!isRequested(request, FetchRequest.MyTeam))
                queueFetchMyTeam();
        }

        private bool hasSomethingToFetch()
        {
            return _fetchQueue.Count > 0;
        }
    
        private bool isRequested(FetchRequest fullRequest, FetchRequest requestSingleItem)
        {
            return (fullRequest & requestSingleItem) == requestSingleItem;
        }

        private void validateFetchQueueItem(FetchRequestQueueItem fetchQueueItem)
        {
            if (fetchQueueItem.DummyItem)
                return;

            if (string.IsNullOrEmpty(fetchQueueItem.UrlToFetch) )
                throw new Exception("URL cannot be null");

            if (fetchQueueItem.FetchFinishedCallback == null)
                throw new Exception("Callback on finish cannot be null");
        }

        private int OnFetchParameterNeeded(FetchRequest fetchRequest)
        {
            if (FetchParameterNeeded != null)
                return FetchParameterNeeded(fetchRequest);
            else
                return -1;
        }

        private void onEvent<T>(EventHandler<T> e, T args) where T : EventArgs
        {
            if (e != null)
                ThreadPool.QueueUserWorkItem(state => e(this, args));
        }

        private void fetchThread()
        {
            initFetcherThread();

            Debug.WriteLine("Starting Fetch");
            while (fetchQueueHasItems())
            {
                try
                {
                    fetchNextItem();
                }
                catch
                {
                    // TODO: call callback with the excpetion?
                    continue;
                }
                finally
                {
                    dequeueFetchItem();
                    onEvent(FetchProgress, new EventArgs());
                }
            }

            try
            {
                setFetchResult();
                onEvent(FetchComplete, _fetchResult);
            }
            catch { }
            finally
            {
                _fetching = false;
            }
 
            Debug.WriteLine("End Fetch");
        }

        private void initFetcherThread()
        {
            Thread.CurrentThread.IsBackground = true;
            _fetching = true;
            _xmls.Clear();
        }

        private void transferUserQueueToFetchQueue()
        {
            while (_userFetchQueue.Count > 0)
                _fetchQueue.Enqueue(_userFetchQueue.Dequeue());
        }

        private void setFetchResult()
        {
            if (_xmls.Count == 0)
                return;

            DateTime latestFetchDate = readLastFetchedDate();
            _fetchResult = new FetchResultEventArgs()
            {
                MyTeamId = _myTeamId,
                MyUserId = Convert.ToInt32(_xmls.First().XPathSelectElement("/HattrickData/UserID").Value),
                HattrickTime = latestFetchDate,
                OffsetFromHattrickTime = DateTime.Now - latestFetchDate,
                Request = _lastRequest,
                Xmls = _xmls,
            };            
        }

        private DateTime readLastFetchedDate()
        {
            if (_xmls.Count == 0)
                return DateTime.MinValue;

            HattrickXmlReader reader = new HattrickXmlReader(_xmls.Last());
            return reader.ReadDate("FetchedDate");
        }
                
        private void dequeueFetchItem()
        {
            lock (_fetchQueue)
            {
                _fetchQueue.Dequeue();
            }
        }

        private bool fetchQueueHasItems()
        {
            return _fetchQueue.Count > 0;
        }

        private void fetchNextItem()
        {
            lock (_fetchQueue)
            {
                var fetchQueueItem = _fetchQueue.Peek();
                validateFetchQueueItem(fetchQueueItem);

                if (fetchQueueItem.PreFetchCallback != null)
                    fetchQueueItem.PreFetchCallback(fetchQueueItem);

                if (fetchQueueItem.DummyItem)
                    return;

                Debug.Write(string.Format("{0}: Downloading {1} ... ", DateTime.Now, fetchQueueItem.UrlToFetch));
                var result = _auth.AccessProtectedResource(fetchQueueItem.UrlToFetch);
                Debug.WriteLine("Done");
                fetchQueueItem.FetchFinishedCallback(result);
            }
        }

        private void queueFetchMyTeam()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=teamdetails&version=2.6",
                FetchFinishedCallback = xml =>
                {
                    _xmls.Add(xml);
                    _myTeamId = Convert.ToInt32(xml.XPathSelectElement("/HattrickData/Team/TeamID").Value);
                    _myTeamLeagueId = Convert.ToInt32(xml.XPathSelectElement("/HattrickData/Team/League/LeagueID").Value);
                }
            });
        }

        private void queueFetchMyPlayers()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=players&version=2.2&includeMatchInfo=true",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        private void queueFetchMyYouthPlayers()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=youthplayerlist&version=1.0&actionType=details&showLastMatch=true",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }
 
        private void queueFetchMyCountry()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=worlddetails&leagueid={0}",
                PreFetchCallback = fetchRequest =>
                {
                    if (_myTeamLeagueId < 0)
                        throw new Exception("Missing league ID");
                    fetchRequest.UrlToFetch = string.Format(fetchRequest.UrlToFetch, _myTeamLeagueId);
                },
                FetchFinishedCallback = xml =>_xmls.Add(xml),
            });
        }

        private void queueFetchMyLeague()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=leaguefixtures",
                FetchFinishedCallback = xml =>
                {
                    _xmls.Add(xml);
                    if (xml.Descendants("HomeTeamID").Count() > 0) // can happen between seasons
                    {
                        _myLeagueOtherTeamIds = (from teamId in xml.Descendants("HomeTeamID")
                                                 where teamId.Value != _myTeamId.ToString()
                                                 select Convert.ToInt32(teamId.Value)).Distinct();
                    }
                    else // fallback to leaguedeatails.xml
                    {
                        _myLeagueOtherTeamIds = from teamId in xml.Descendants("TeamID")
                                                where teamId.Value != _myTeamId.ToString()
                                                select Convert.ToInt32(teamId.Value);
                        queueFetchMyLeagueDetails();
                    }
                }
            });
        }

        private void queueFetchMyLeagueDetails()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=leaguedetails",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        private void queueFetchMyRegion()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=regiondetails",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        private void queueFetchMyEconomy()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch =  "https://chpp.hattrick.org/chppxml.ashx?file=economy&version=1.2",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });            
        }

        private void queueFetchMyMatches()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=matches&version=2.4",
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });     
        }

        private void queueFetchMyLeagueOtherTeams()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                DummyItem = true,
                PreFetchCallback = fetchRequest =>
                {
                    foreach (int teamId in _myLeagueOtherTeamIds)
                        queueFetchTeam(teamId);                    
                },
            });
        }

        private void queueFetchLive()
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = "https://chpp.hattrick.org/chppxml.ashx?file=live&version=1.5&actionType={0}",
                PreFetchCallback = fetchRequest =>
                {
                    fetchRequest.UrlToFetch = string.Format(fetchRequest.UrlToFetch,
                                                            _fetchingLiveFirstTime ? "viewAll" : "viewNew");
                    _fetchingLiveFirstTime = false;
                },
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        private void queueFetchTeam(int teamId)
        {
            _fetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = string.Format("https://chpp.hattrick.org/chppxml.ashx?file=teamdetails&version=2.6&teamID={0}", teamId),
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        private void queueFetchMatchDetails(int matchId)
        {
            _userFetchQueue.Enqueue(new FetchRequestQueueItem()
            {
                UrlToFetch = string.Format("https://chpp.hattrick.org/chppxml.ashx?file=matchdetails&version=2.0&matchEvents=true&matchID={0}",
                                            matchId),
                FetchFinishedCallback = xml => _xmls.Add(xml),
            });
        }

        public void QueueMatchDetailsById(int matchId)
        {
            queueFetchMatchDetails(matchId);
        }

        public void QueueTeamById(int teamId)
        {
            queueFetchTeam(teamId);
        }

        #region Synchronous Fetches

        public Team MyTeam()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=teamdetails&version=2.6");
            return Team.FromXml(xml);
        }

        public Region MyRegion()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=regiondetails&version=1.2");
            return Region.FromXml(xml);
        }

        public EntityCollection<Player> MyPlayers()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=players&version=2.0&includeMatchInfo=true");
            return Player.FromXml(xml);
        }


        public EntityCollection<YouthPlayer> MyYouthPlayers()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=youthplayerlist&version=1.0&actionType=details&showLastMatch=true");
            return YouthPlayer.FromXml(xml);
        }

        public EntityCollection<Match> MyMatches()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=matches&version=2.4");
            return Match.FromXml(xml);
        }

        public Economy MyEconomy()
        {
            var xml = _auth.AccessProtectedResource("https://chpp.hattrick.org/chppxml.ashx?file=economy&version=1.2");
            return Economy.FromXml(xml);
        }

        public DetailedMatch MatchDetails(int matchID)
        {
            var xml = _auth.AccessProtectedResource(string.Format("https://chpp.hattrick.org/chppxml.ashx?file=matchdetails&version=2.0&matchEvents=true&matchID={0}",
                                                    matchID));
            return DetailedMatch.FromXml(xml);
        }

        public Player PlayerById(int playerId)
        {
            var xml = _auth.AccessProtectedResource(string.Format("https://chpp.hattrick.org/chppxml.ashx?file=playerdetails&version=1.9&playerID={0}&includeMatchInfo=true", 
                                                    playerId));
            return Player.FromXml(xml).FirstOrDefault();
        }

        #endregion        
  
    }
}


       




